package com.ysy.tz.core.validator;

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.transform.Transform;

public class GraphicDecoration implements Decoration {
    private Node decoratedNode;
    private final Node decorationNode;
    private final Pos pos;
    private final double xOffset;
    private final double yOffset;
    private final ChangeListener<Boolean> layoutListener;
    private final ChangeListener<Transform> transformListener;
    private ChangeListener<Scene> sceneChangedListener;
    private GraphicDecorationStackPane stack;

    public GraphicDecoration(Node decorationNode) {
        this(decorationNode, Pos.TOP_LEFT);
    }

    public GraphicDecoration(Node decorationNode, Pos position) {
        this(decorationNode, position, 0.0, 0.0);
    }

    public GraphicDecoration(Node decorationNode, Pos position, double xOffset, double yOffset) {
        this.decorationNode = decorationNode;
        this.decorationNode.setManaged(false);
        this.pos = position;
        this.xOffset = xOffset;
        this.yOffset = yOffset;
        this.layoutListener = (observable, oldValue, newValue) -> {
            this.layoutGraphic();
        };
        this.transformListener = (observable, oldValue, newValue) -> {
            this.layoutGraphic();
        };
    }

    public static void updateDecorations(Node node) {
        GraphicDecorationStackPane decorationPane = findDecorationPane(node);
        if (decorationPane != null) {
            decorationPane.requestLayout();
        }

    }

    public void add(Node target) {
        this.decoratedNode = target;
        this.withStack(() -> {
            if (this.decoratedNode != null) {
                this.setListener();
                this.layoutGraphic();
            }

        }, target);
    }

    public void remove(Node target) {
        if (this.stack != null) {
            this.stack.getChildren().remove(this.decorationNode);
            this.stack.needsLayoutProperty().removeListener(this.layoutListener);
        }

        this.decoratedNode = null;
        target.localToSceneTransformProperty().removeListener(this.transformListener);
    }

    private void withStack(Runnable code, Node target) {
        this.stack = findDecorationPane(target);
        if (this.stack == null) {
            if (target.getScene() == null) {
                this.sceneChangedListener = (observable, oldValue, newValue) -> {
                    if (oldValue == null && newValue != null) {
                        target.sceneProperty().removeListener(this.sceneChangedListener);
                        this.setupStack(target);
                        code.run();
                    }

                };
                target.sceneProperty().addListener(this.sceneChangedListener);
            } else {
                this.setupStack(target);
                code.run();
            }
        } else {
            code.run();
        }

    }

    private static GraphicDecorationStackPane findDecorationPane(Node node) {
        Parent parent;
        for(parent = node.getParent(); parent != null && !(parent instanceof GraphicDecorationStackPane); parent = parent.getParent()) {
        }

        return (GraphicDecorationStackPane)parent;
    }

    private void setupStack(Node target) {
        this.stack = new GraphicDecorationStackPane();
        Parent oldRoot = target.getScene().getRoot();
        target.getScene().setRoot(this.stack);
        this.stack.getChildren().add(oldRoot);
    }

    private void setListener() {
        this.stack.needsLayoutProperty().removeListener(this.layoutListener);
        this.stack.needsLayoutProperty().addListener(this.layoutListener);
        this.decoratedNode.localToSceneTransformProperty().removeListener(this.transformListener);
        this.decoratedNode.localToSceneTransformProperty().addListener(this.transformListener);
    }

    private void layoutGraphic() {
        this.decorationNode.autosize();
        Bounds decorationNodeLayoutBounds = this.decorationNode.getLayoutBounds();
        double decorationNodeWidth = decorationNodeLayoutBounds.getWidth();
        double decorationNodeHeight = decorationNodeLayoutBounds.getHeight();
        Bounds targetBounds = this.decoratedNode.getLayoutBounds();
        double x = targetBounds.getMinX();
        double y = targetBounds.getMinY();
        double targetWidth = targetBounds.getWidth();
        if (targetWidth <= 0.0) {
            targetWidth = this.decoratedNode.prefWidth(-1.0);
        }

        double targetHeight = targetBounds.getHeight();
        if (targetHeight <= 0.0) {
            targetHeight = this.decoratedNode.prefHeight(-1.0);
        }

        double var10000;
        switch (this.pos.getHpos()) {
            case CENTER:
                var10000 = x + (targetWidth / 2.0 - decorationNodeWidth / 2.0);
                break;
            case LEFT:
                var10000 = x - decorationNodeWidth / 2.0;
                break;
            case RIGHT:
                var10000 = x + (targetWidth - decorationNodeWidth / 2.0);
        }

        switch (this.pos.getVpos()) {
            case CENTER:
                var10000 = y + (targetHeight / 2.0 - decorationNodeHeight / 2.0);
                break;
            case TOP:
                var10000 = y - decorationNodeHeight / 2.0;
                break;
            case BOTTOM:
                var10000 = y + (targetHeight - decorationNodeHeight / 2.0);
                break;
            case BASELINE:
                var10000 = y + (this.decoratedNode.getBaselineOffset() - this.decorationNode.getBaselineOffset() - decorationNodeHeight / 2.0);
        }

        x = -5.0;
        y = -7.6201171875;
        Bounds sceneBounds = this.decoratedNode.localToScene(targetBounds);
        Bounds stackBounds = this.stack.sceneToLocal(sceneBounds);
        this.decorationNode.setLayoutX((double)Math.round(x + this.xOffset + stackBounds.getMinX()));
        this.decorationNode.setLayoutY((double)Math.round(y + this.yOffset + stackBounds.getMinY()));
        this.decorationNode.resize(10.0, 15.240234375);
        this.addOrRemoveDecorationNodeToStack();
    }

    private void addOrRemoveDecorationNodeToStack() {
        if (this.stack != null) {
            boolean shouldBeThere = this.decoratedNode.getScene() != null && this.targetVisible();
            boolean isThere = this.stack.getChildren().contains(this.decorationNode);
            if (shouldBeThere != isThere) {
                if (shouldBeThere) {
                    this.stack.getChildren().add(this.decorationNode);
                } else {
                    Platform.runLater(() -> {
                        this.stack.getChildren().remove(this.decorationNode);
                    });
                }
            }
        }

    }

    private boolean targetVisible() {
        Node node = this.decoratedNode;

        boolean visible;
        for(visible = true; visible && node != null; node = ((Node)node).getParent()) {
            visible = ((Node)node).isVisible();
        }

        return visible;
    }
}
